const { ccclass, property } = cc._decorator;

@ccclass
export default class A_asterisk extends cc.Component {
    @property(cc.Node)
    ctxCanvas: cc.Node = null;
    @property(cc.Node)
    startPointBtn: cc.Node = null;
    @property(cc.Node)
    endPointBtn: cc.Node = null;
    @property(cc.Node)
    wallPointBtn: cc.Node = null;
    @property(cc.Node)
    calculatePathBtn: cc.Node = null;
    @property(cc.Node)
    clearWallBtn: cc.Node = null;
    @property(cc.Node)
    traceBtn: cc.Node = null;
    count = 0;
    @property
    randomWall = true;
    @property
    randomWallRate = 0.2;
    @property
    isStep = false;
    @property
    isDebug = false;
    @property
    walkSideways = false;
    @property(cc.Toggle)
    isStepToggle: cc.Toggle = null;
    @property(cc.Toggle)
    isDebugToggle: cc.Toggle = null;
    @property
    rows: number = 50;
    @property
    cols: number = 50;
    ctx: cc.Graphics = null;
    gridSize: cc.Size = null;
    gridArray = [];
    @property
    startPoint: cc.Vec2 = cc.v2(0, 0);
    @property
    endPoint: cc.Vec2 = cc.v2(1, 1);

    playSpeed = 0.0;
    lineCtx: cc.Graphics = null;
    directions = [cc.v2(0, 1), cc.v2(0, -1), cc.v2(1, 0), cc.v2(-1, 0), cc.v2(1, 1), cc.v2(1, -1), cc.v2(-1, 1), cc.v2(-1, -1)];

    // directions = [cc.v2(0, 1), cc.v2(0, -1), cc.v2(1, 0), cc.v2(-1, 0)];
    onLoad() {

        window['scene'] = this;
        // console.log('this.gridArray', this.gridArray)
        // 初始化画笔
        let ctxNode = new cc.Node('ctx');
        ctxNode.parent = this.ctxCanvas;
        ctxNode.position = cc.v2(0, 0);
        let ctx = ctxNode.addComponent(cc.Graphics);
        ctx.lineWidth = 5;
        ctx.strokeColor = cc.color(0, 0, 255);
        ctx.fillColor = cc.color(0, 0, 0);
        this.ctx = ctx;


        let lineCtxNode = new cc.Node('ctx');
        lineCtxNode.parent = this.ctxCanvas;
        lineCtxNode.position = cc.v2(0, 0);
        let lineCtx = lineCtxNode.addComponent(cc.Graphics);
        lineCtx.lineWidth = 5;
        lineCtx.strokeColor = cc.color(255, 255, 255);
        lineCtx.fillColor = cc.color(0, 0, 0);
        this.lineCtx = lineCtx;

        // 初始化二维数组
        this.gridArray = [];
        for (let i = 0; i < this.rows; i++) {
            let arr = [];
            for (let j = 0; j < this.cols; j++) {
                let a = { type: 'none' };
                if (this.randomWall && Math.random() < this.randomWallRate) {
                    a.type = 'wallPoint'
                }
                arr.push(a);
            }
            this.gridArray.push(arr);
        }
        this.ctxCanvas.setContentSize(cc.winSize.width * 0.7, cc.winSize.height);
        this.gridSize = cc.size(this.ctxCanvas.width / this.cols, this.ctxCanvas.height / this.rows);
        // this.startPoint = cc.v2(2, 10);
        // this.endPoint = cc.v2(18, 10);
        this.isStepToggle.isChecked = this.isStep;
        this.isDebugToggle.isChecked = this.isDebug;
        this.isStepToggle.node.on(cc.Node.EventType.TOUCH_END, () => {
            this.scheduleOnce(() => {
                this.isStep = this.isStepToggle.isChecked;
            })
        })
        this.isDebugToggle.node.on(cc.Node.EventType.TOUCH_END, () => {
            this.scheduleOnce(() => {
                this.isDebug = this.isDebugToggle.isChecked;
            })
        })
        this.init();
    }

    init() {
        this.directions = this.walkSideways
            ? [cc.v2(0, 1), cc.v2(0, -1), cc.v2(1, 0), cc.v2(-1, 0), cc.v2(1, 1), cc.v2(1, -1), cc.v2(-1, 1), cc.v2(-1, -1)]
            : [cc.v2(0, 1), cc.v2(0, -1), cc.v2(1, 0), cc.v2(-1, 0)];

        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                let pos = new cc.Vec2(
                    row * this.gridSize.width,
                    col * this.gridSize.height,
                )
                let data = this.gridArray[row][col];
                data['pos'] = pos;
                data.row = row;
                data.col = col;
                if (this.startPoint && row == this.startPoint.x && this.startPoint.y == col) {
                    data.type = 'startPoint';
                }
                if (this.endPoint && row == this.endPoint.x && this.endPoint.y == col) {
                    data.type = 'endPoint';
                }
            }
        }
        this.updateGrids();

        // 监听事件
        this.startPointBtn.on(cc.Node.EventType.TOUCH_START, function () {
            // console.log('setStartPointBtn')
            this.node.targetOff(this);
            this.node.on(cc.Node.EventType.TOUCH_START, this.setStartPoint, this);
            this.node.on(cc.Node.EventType.TOUCH_MOVE, this.setStartPoint, this);
        }, this);
        this.endPointBtn.on(cc.Node.EventType.TOUCH_START, function () {
            // console.log('setEndPointBtn')
            this.node.targetOff(this);
            this.node.on(cc.Node.EventType.TOUCH_START, this.setEndPoint, this);
            this.node.on(cc.Node.EventType.TOUCH_MOVE, this.setEndPoint, this);
        }, this);

        this.wallPointBtn.on(cc.Node.EventType.TOUCH_START, function () {
            // console.log('setwallPointBtn')
            this.node.targetOff(this);
            this.node.on(cc.Node.EventType.TOUCH_START, this.setWallPoint, this);
            this.node.on(cc.Node.EventType.TOUCH_MOVE, this.setWallPoint, this);
        }, this);
        this.clearWallBtn.on(cc.Node.EventType.TOUCH_START, this.clearWallPoint, this);
        this.calculatePathBtn.on(cc.Node.EventType.TOUCH_START, this.calculatePath, this);
        this.calculatePathBtn.on(cc.Node.EventType.TOUCH_START, this.calculatePath, this);
        this.traceBtn.on(cc.Node.EventType.TOUCH_START, this.traceEvent, this);
    }

    // 画一个格子
    drawOneRect(x, y, color) {
        let ctx = this.ctx;
        let gridSize = this.gridSize;
        ctx.rect(x, y, gridSize.width, gridSize.height);
        ctx.fillColor = color || new cc.Color(0, 0, 0);
        ctx.stroke();
        ctx.fill();
    }

    // 画一个小矩形
    drawOneSmallRect(x, y, color) {
        let ctx = this.ctx;
        let gridSize = this.gridSize;
        ctx.rect(x, y, gridSize.width * 0.6, gridSize.height * 0.6);
        ctx.fillColor = color || new cc.Color(0, 0, 0);
        ctx.stroke();
        ctx.fill();
    }

    // 画一个小矩形
    drawLineByIdxs(posIdxArray: AStarPoint[]) {
        let ctx = this.lineCtx;
        ctx.clear();
        let cur_p = posIdxArray[0];
        let startPos = this.gridArray[cur_p.x][cur_p.y]['pos'];
        let offset = this.gridSize.width / 4;
        ctx.moveTo(startPos.x + offset, startPos.y + offset);
        // console.log('pos', cur_p);
        for (let i = 0; i < posIdxArray.length; i++) {
            // this.scheduleOnce(() => {
            let curPoint = posIdxArray[i];
            let pos = this.gridArray[curPoint.x][curPoint.y]['pos'];
            ctx.lineTo(pos.x + offset, pos.y + offset);
            // console.log('pos', pos);
            // }, i * 0.01)
        }
        ctx.stroke();
    }


    // 更新路径图
    updateGrids() {
        // console.log('updateGrids')
        let ctx = this.ctx;
        ctx.clear();
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                let data = this.gridArray[row][col];
                // console.log('data', data)
                let pos = data['pos'];
                let color = data['color'];
                // console.log('pos', pos);
                switch (data.type) {
                    case 'wallPoint': color = cc.color(171, 84, 34); break;
                    case 'startPoint': color = cc.color(0, 255, 0); break;
                    case 'endPoint': color = cc.color(255, 0, 0); break;
                    default: color = cc.color(0, 0, 0); break;
                }
                this.drawOneRect(pos.x, pos.y, color);
                if (data.process) {
                    switch (data.process) {
                        case 'open': color = cc.color(255, 255, 0, 150); break;
                        case 'close': color = cc.color(0, 255, 255, 150); break;
                    }
                    this.drawOneSmallRect(pos.x, pos.y, color)
                }
            }
        }
    }

    // 设置起始点
    setStartPoint(touch) {
        if (this.isRunnning) return;
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                // delete this.gridArray[row][col].process;
                if (this.gridArray[row][col].type == 'startPoint') {
                    this.gridArray[row][col].type = 'none';
                }
                let pos = this.gridArray[row][col].pos;
                let rect = new cc.Rect(pos.x, pos.y, this.gridSize.width, this.gridSize.height);
                if (rect.contains(touch.getLocation())) {
                    // console.log('setStartPoint', row, col);
                    this.gridArray[row][col].type = 'startPoint';
                    this.startPoint = cc.v2(row, col);
                }
            }
        }
        // this.calculatePath();
        this.updateGrids();
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                delete this.gridArray[row][col].process;
            }
        }
        this.calculatePath()
    }
    // 设置目的地
    setEndPoint(touch) {
        if (this.isRunnning) return;
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                // delete this.gridArray[row][col].process;
                if (this.gridArray[row][col].type == 'endPoint') {
                    this.gridArray[row][col].type = 'none';
                }
                let pos = this.gridArray[row][col].pos;
                let rect = new cc.Rect(pos.x, pos.y, this.gridSize.width, this.gridSize.height);
                if (rect.contains(touch.getLocation())) {
                    // console.log('setEndPoint', row, col);
                    this.gridArray[row][col].type = 'endPoint';
                    this.endPoint = cc.v2(row, col);
                }
            }
        }
        // this.calculatePath();
        this.updateGrids();
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                delete this.gridArray[row][col].process;
            }
        }
        this.calculatePath()
    }
    // 设置墙壁
    setWallPoint(touch) {
        if (this.isRunnning) return;
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                // if (this.gridArray[row][col].type == 'wallPoint') {
                //     this.gridArray[row][col].type = 'none';
                // }
                let pos = this.gridArray[row][col].pos;
                let rect = new cc.Rect(pos.x, pos.y, this.gridSize.width, this.gridSize.height);
                if (rect.contains(touch.getLocation())
                    && this.gridArray[row][col].type == 'none'
                ) {
                    // console.log('setwallPoint', row, col);
                    this.gridArray[row][col].type = 'wallPoint';
                    // this.endPoint = cc.v2(row, col);
                }
            }
        }
        // this.calculatePath();
        this.updateGrids();
    }

    clearWallPoint() {
        if (this.isRunnning) return;
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                if (this.gridArray[row][col].type == 'wallPoint') {
                    this.gridArray[row][col].type = 'none';
                }
                delete this.gridArray[row][col].process;
            }
        }
        // this.calculatePath();
        if (this.lineCtx) {
            this.lineCtx.clear()
        }
        this.updateGrids();
    }
    tracePath: AStarPoint[] = []
    /**开始寻路 */
    traceEvent() {
        if (this.tracePath.length) {
            this.schedule(this.moveToNextArea, 0.1)
        }
    }

    moveToNextArea() {
        if (this.tracePath.length > 1) {
            let p = this.tracePath.shift()
            this.gridArray[this.startPoint.x][this.startPoint.y].type = 'none';

            this.startPoint = cc.v2(p.x, p.y);
            this.gridArray[p.x][p.y].type = 'startPoint';

            this.drawLineByIdxs(this.tracePath);
            // this.calculatePath()
            this.updateGrids();
        } else {
            this.unschedule(this.moveToNextArea);
        }
    }
    /**
     * a*算法 计算最短路径
     */

    // @property(cc.Label)
    // calculateTimeLabel: cc.Label = null;
    calculateTime: number = 0;
    calculateCount: number = 0;
    isCalculate: boolean = false;
    updateTimeCost() {
        // console.log(this.calculateTime);
        this.isCalculate = false;
        // this.calculateTimeLabel.string = '耗时：' + (this.calculateTime / this.calculateCount).toString() + '秒';
    }
    /**曼哈顿距离 */
    getManhattanDist(xOrVec2: number | cc.Vec2, y?: number) {
        if (xOrVec2 instanceof cc.Vec2) {
            return Math.abs(xOrVec2.x) + Math.abs(xOrVec2.y)
        } else {
            return Math.abs(xOrVec2) + Math.abs(y)
        }
    }

    isRunnning = false;
    hasWallAround(p: cc.Vec2) {

        return true
    }
    // 回调方式
    calculatePath() {
        if (this.isRunnning) return;
        this.isRunnning = true;

        this.calculateCount++;
        for (let row = 0; row < this.gridArray.length; row++) {
            for (let col = 0; col < this.gridArray[row].length; col++) {
                delete this.gridArray[row][col].process;
            }
        }
        if (this.lineCtx) {
            this.lineCtx.clear()
        }
        // console.log('计算路径');
        this.count = 0;
        // 初始化列表
        let startPoint = new AStarPoint(this.startPoint.x, this.startPoint.y, null);
        let endPoint = new AStarPoint(this.endPoint.x, this.endPoint.y, null);
        let openList = new Array<AStarPoint>();
        let closeList = [];
        openList.push(startPoint);
        let checkAround = (callback: Function) => {
            this.count = this.count || 0;
            this.count++;
            if (this.count > 5000) {
                // console.error('超过次数')
                this.updateTimeCost();
                if (callback) callback();
                return;
            }
            if (openList.length > 0) {
                // debugger
                // a) 寻找开启列表中F值最低的格子。我们称它为当前格
                window['openList'] = openList;
                // 如果列表不为空,查找F值最小的那个点
                let minF: number = 999, F: number, parentPoint: AStarPoint;
                openList.sort((a, b) => {
                    return a.F - b.F;
                })
                parentPoint = openList[0]
                // b) 把它切换到关闭列表
                let idx = openList.indexOf(parentPoint)
                if (idx != -1) {
                    parentPoint = openList.splice(idx, 1)[0];
                } else {
                    console.log('no idx')
                }

                // this.directions.push(this.directions.shift());
                if (parentPoint.x == endPoint.x && parentPoint.y == endPoint.y) {
                    // 该节点是目的地节点,就找完了，然后循环找到其父节点形成路径
                    // console.log('找到了');
                    let list = parentPoint.getParentList();
                    list.unshift(endPoint);
                    this.updateGrids();
                    this.drawLineByIdxs(list);
                    this.updateTimeCost();
                    this.tracePath = list;
                    if (callback) callback();
                } else {
                    // c) 对相邻的格中的每一个
                    // 将point从openList中删除，加入到closeList
                    delete this.gridArray[parentPoint.x][parentPoint.y]['process'];
                    this.gridArray[parentPoint.x][parentPoint.y]['process'] = 'close';
                    closeList.push(parentPoint);
                    // 获取周围的点
                    let directions = this.directions;
                    // directions
                    let isNextToWall = false;
                    for (let i of directions) {
                        let curPoint = cc.v2(parentPoint.x + i.x, parentPoint.y + i.y);
                        // 忽略在closeList、openList、超出边界的、不可通过的
                        let res = closeList.find((v, i) => { return v.x == curPoint.x && v.y == curPoint.y; })
                        if (res) { continue; }
                        let openRes = openList.find((v, i) => { return v.x == curPoint.x && v.y == curPoint.y; })
                        if (openRes) { continue; }
                        if (!this.gridArray[curPoint.x] || !this.gridArray[curPoint.x][curPoint.y]) { continue; }
                        let data = this.gridArray[curPoint.x][curPoint.y];
                        // 墙壁
                        if (data.type == 'wallPoint') {
                            // console.log('不可通过', curPoint.x, curPoint.y)
                            isNextToWall = true;
                            continue;
                        }

                        let aStarPoint = new AStarPoint(data.row, data.col, parentPoint);
                        let G: number, H: number;
                        // if (this.hasWallAround(curPoint)) {
                        //     // 如果有墙壁在周围就不能斜着走
                        //     continue;
                        // }
                        let G_dir = curPoint.sub(this.startPoint);
                        let H_dir = curPoint.sub(this.endPoint);
                        //欧式距离
                        G = parentPoint.G + i.mag();
                        H = H_dir.mag();
                        //曼哈顿距离
                        // G = this.getManhattanDist(G_dir)
                        // H = this.getManhattanDist(H_dir)
                        // 计算F值
                        aStarPoint.F = G + H;
                        aStarPoint.G = G;
                        aStarPoint.H = H;
                        // aStarPoint.dirX = dir.x;
                        // aStarPoint.dirY = dir.y;
                        // 放在待检测列表
                        openList.push(aStarPoint)
                        // 变为待检测节点
                        delete this.gridArray[curPoint.x][curPoint.y]['process'];
                        this.gridArray[curPoint.x][curPoint.y]['process'] = 'open';
                    }
                    // 再次检测，并且绘制一次界面
                    if (this.isStep) {
                        this.scheduleOnce(() => {
                            checkAround(callback);
                            this.updateGrids();
                            if (this.isDebug) {
                                debugger
                            }
                        }, 0)
                    } else {
                        checkAround(callback);
                    }
                }
            } else {
                console.log('openList 为空 没找到')
                this.updateTimeCost();
                if (callback) callback();
            }
        }
        // console.log('openList', openList)
        checkAround(() => {
            this.isRunnning = false;
        });
    }

    dijkstra_calc() {

    }

    dt: number = 0;
    update(dt) {
        this.dt = dt;
        if (this.isCalculate) {
            this.calculateTime += dt;
            console.log('this.calculateTime', dt);
        }
    }
}


class AStarPoint {
    x = 0;
    y = 0;
    F = 0;
    G = 0;
    H = 0;
    // dirX = null;
    // dirY = null;
    parent: AStarPoint = null;
    costDist: number = 0;
    constructor(x: number, y: number, parent: AStarPoint) {
        this.x = x;
        this.y = y;
        this.parent = parent;
    }

    getParentList(): AStarPoint[] {
        let list = [];
        if (this.parent) {
            list.push(this.parent);
            list = list.concat(this.parent.getParentList());
        }
        return list;
    }

}
